/*	--- MVC高级开发 --- 

通过结合Servlet和JSP的MVC模式，我们可以发挥二者各自的优点：
	· Servlet实现【业务逻辑】；
	· JSP实现【展示逻辑】。


但是，直接把【MVC】搭在【Servlet和JSP】之上还是【不太好】，原因如下：

	· 【Servlet提供的接口】仍然【偏底层】，需要【实现Servlet】调用【相关接口】；

	· 【JSP】对【页面开发】不友好，更好的替代品是【模板引擎】；

	· 【业务逻辑】最好由【纯粹的Java类】实现，而不是【强迫继承自Servlet】。


能不能通过【普通的Java类】实现【MVC的Controller】？
类似下面的代码： */
public class UserController {
	@GetMapping("/signin")
	public ModelAndView signin() {
		//...
	}

	@PostMapping("/signin")
	public ModelAndView doSignin(SignInBean bean) {
		//...
	}

	@GetMapping("/signout")
	public ModelAndView signout(HttpSession session) {
		//...
	}
}

/*
上面的这个【Java类の每个方法】都对应【一个GET/POST请求】，【方法返回值】是【ModelAndView】，它包含【一个View的路径】以及【一个Model】

这样，再由【MVC框架】处理后,返回给【浏览器】。

--------------------------

如果是【GET请求】，我们希望【MVC框架】能直接把【URL参数】按【方法参数】对应起来然后传入： */
@GetMapping("/hello")
public ModelAndView hello(String name) {
	//...
}


/*
如果是【POST请求】，我们希望【MVC框架】能直接把【Post参数】变成【一个JavaBean】后通过【方法参数】传入： */
@PostMapping("/hello")
public ModelAndView doSignin(SignInBean bean) {
	//...
}

/*
为了【增加灵活性】，如果【Controller的方法】在【处理请求】时,需要【访问HttpServletRequest、HttpServletResponse、HttpSession这些实例】时

只要【方法参数】有定义，就可以【自动传入】： */
@GetMapping("/signout")
public ModelAndView signout(HttpSession session) {
	//...
}

/*
以上就是我们在【设计MVC框架】时，【上层代码】所需要的【一切信息】。


=========================================================


#	设计MVC框架

Q: 如何设计【一个MVC框架】？
A: 在上文中，我们已经定义了【上层代码编写Controller的一切接口信息】，并且并【不要求实现特定接口】，只需【返回ModelAndView对象】

【ModelAndView对象】包含【一个View】和【一个Model】。

实际上
	【View】就是【模板的路径】，
而	【Model】可以用【一个Map<String, Object>】表示。

因此，【ModelAndView】定义非常简单： */
public class ModelAndView {
	Map<String, Object> model;
	String view;
}


/*
比较复杂的是: 我们需要在【MVC框架】中, 创建一个【接收所有请求的Servlet】，通常我们把它命名为【DispatcherServlet】。

【DispatcherServlet】总是映射到【/ 根目录】，然后，根据【不同的Controller的方法】定义的【@Get / @Post】的Path决定【调用哪个方法】

最后，获得【方法返回的ModelAndView】后，【渲染Model模板】，写入【HttpServletResponse】，即完成了【整个MVC的处理】。

这个【MVC的架构】如下：

	   HTTP Request    ┌─────────────────┐
	──────────────────>│DispatcherServlet│
	                   └─────────────────┘
	                            │
	               ┌────────────┼────────────┐
	               ▼            ▼            ▼
	         ┌───────────┐┌───────────┐┌───────────┐
	         │Controller1││Controller2││Controller3│
	         └───────────┘└───────────┘└───────────┘
	               │            │            │
	               └────────────┼────────────┘
	                            ▼
	   HTTP Response ┌────────────────────┐
	<────────────────│render(ModelAndView)│
	                 └────────────────────┘


其中，【DispatcherServlet】以及【如何渲染】均由【MVC框架】实现，在【MVC框架】之上只需要【编写每一个Controller】。

--------------------------

我们来看看【如何编写"最复杂的DispatcherServlet"】。

首先，我们需要存储【请求路径 到 某个具体方法 的映射】： */
@WebServlet(urlPatterns = "/")
public class DispatcherServlet extends HttpServlet {
	private Map<String, GetDispatcher> getMappings = new HashMap<>();
	private Map<String, PostDispatcher> postMappings = new HashMap<>();
}


/*
处理一个【GET请求】, 是通过【GetDispatcher对象】完成的，它需要【如下信息】：*/
class GetDispatcher {
	Object instance; // Controller实例
	Method method; // Controller方法
	String[] parameterNames; // 方法参数_名称
	Class<?>[] parameterClasses; // 方法参数_类型
}

/*
有了以上信息，就可以【定义invoke()方法】来处理【真正的请求】： */
class GetDispatcher {
	Object instance; // Controller实例
	Method method; // Controller方法
	String[] parameterNames; // 方法参数_名称
	Class<?>[] parameterClasses; // 方法参数_类型

	public ModelAndView invoke(HttpServletRequest request, HttpServletResponse response) {

		Object[] arguments = new Object[parameterClasses.length];

		for (int i=0; i<parameterClasses.length; i++) {

			String parameterName = parameterNames[i];
			Class<?> parameterClass = parameterClasses[i];

			if (parameterClass == HttpServletRequest.class) {
				arguments[i] = request;
			} else if (parameterClass == HttpServletResponse.class) {
				arguments[i] = response;
			} else if (parameterClass == HttpSession.class) {
				arguments[i] = request.getSession();
			} else if (parameterClass == int.class) {
				arguments[i] = Integer.valueOf(getOrDefault(request, parameterName, "0"));
			} else if (parameterClass == long.class) {
				arguments[i] = Long.valueOf(getOrDefault(request, parameterName, "0"));
			} else if (parameterClass == boolean.class) {
				arguments[i] = Boolean.valueOf(getOrDefault(request, parameterName, "false"));
			} else if (parameterClass == String.class) {
				arguments[i] = getOrDefault(request, parameterName, "");
			} else {
				throw new RuntimeException("Missing handler for type: " + parameterClass);
			}
		}

		return (ModelAndView)this.method.invoke(this.instance, arguments);
	}

	private String getOrDefault(HttpServletRequest request, String name, String defaultValue) {

		String s = request.getParameter(name);

		return s == null ? defaultValue : s; // 若无该参数,则return默认值
	}
}


/*
【上述代码】比较繁琐，但【逻辑】非常简单，即通过【构造某个方法】需要的【所有参数列表】，使用【反射】调用【该方法】后返回结果。

类似的，【PostDispatcher】需要【如下信息】： */
class PostDispatcher {
    Object instance; // Controller实例
    Method method; // Controller方法
    Class<?>[] parameterClasses; // 方法参数类型
    ObjectMapper objectMapper; // JSON映射
}


/*
和【GET请求】不同，【POST请求】严格地来说【不能有URL参数】。

【POST请求】的【所有数据】都应当从【Post Body】中读取。

这里我们【为了简化处理】，只支持【JSON格式的POST请求】

这样，把【Post数据】转化为【JavaBean】就非常容易。 */
class PostDispatcher {
	Object instance // Controller实例
	Method method; // Controller方法
	Class<?>[] parameterClasseds; // 方法参数_类型
	ObjectMapper objectMapper; // JSON映射

	public ModelAndView invoke(HttpServletRequest request, HttpServletResponse response) {

		Object[] arguments = new Object[parameterClasses.length];

		for (int i=0; i<parameterClasses.length; i++) {

			Class<?> parameterClass = parameterClasses[i];

			if (parameterClass == HttpServletRequest.class) {
				arguments[i] = request;
			} else if (parameterClass == HttpServletResponse.class) {
				arguments[i] = response;
			} else if (parameterClass == HttpSession.class) {
				arguments[i] = request.getSession();
			} else {
				// 读取JSON,并解析为JavaBean
				BufferedReader reader = request.getReader();
				arguments[i] = this.objectMapper.readValue(reader, parameterClass); 
					// arguments[i] = JSON映射
					// parameterClass = parameterClasses[i];
			}
		}

		return (ModelAndView)this.method.invoke(instance, arguments);
	}
}


/*
最后，我们来实现【整个DispatcherServlet的处理流程】，以【doGet()】为例： */
class PostDispatcher {
	Object instance // Controller实例
	Method method; // Controller方法
	Class<?>[] parameterClasseds; // 方法参数_类型
	ObjectMapper objectMapper; // JSON映射

	public ModelAndView invoke(HttpServletRequest request, HttpServletResponse response) {

		Object[] arguments = new Object[parameterClasses.length];

		for (int i=0; i<parameterClasses.length; i++) {

			Class<?> parameterClass = parameterClasses[i];

			if (parameterClass == HttpServletRequest.class) {
				arguments[i] = request;
			} else if (parameterClass == HttpServletResponse.class) {
				arguments[i] = response;
			} else if (parameterClass == HttpSession.class) {
				arguments[i] = request.getSession();
			} else {
				// 读取JSON,并解析为JavaBean
				BufferedReader reader = request.getReader();
				arguments[i] = this.objectMapper.readValue(reader, parameterClass); 
					// arguments[i] = JSON映射
					// parameterClass = parameterClasses[i];
			}
		}

		return (ModelAndView)this.method.invoke(instance, arguments);
	}

//新加入
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {

		resp.setContentType("text/html");
		resp.setCharacterEncoding("UTF-8");

		String path = req.getRequestURI().substring(req.getContextPath().length());

		// 根据path查找GetDispatcher
		GetDispatcher dispatcher = this.getMappings.get(path);

		if (dispatcher == null) {
			// 未找到,返回404
			resp.sendError(404);
			return ;
		}

		// 调用【Controller方法】,获取【返回值】
		ModelAndView mv = dispatcher.invoke(req, resp);

		// 允许返回null
		if (mv == null) {
			return ;
		}

		// 允许返回'redirect:'开头的view表示【重定向】
		if (mv.view.startsWith("redirect:")) {
			resp.sendRedirect(mv.view.substring(9));
			return ;
		}

		// 将【模板引擎】渲染出来的【内容】写入【响应】
		PrintWriter pw = resp.getWriter();
		this.viewEngine.render(mv, pw);
		pw.flush();
	}
}

/*
这里有【几个小改进】：

· 允许【Controller方法】返回【null】，表示【内部已自行处理完毕】；
· 允许【Controller方法】返回【以redirect:开头的view名称】，表示【一个重定向】。

这样使得【上层代码】编写更灵活。

例如，【一个显示用户资料的请求】可以这样写： */
@GetMapping("/user/profile")
public ModelAndView profile(HttpServletResponse response, HttpSession session) {

	User user = (User)session.getAttribute("user");

	if (user == null) {
		// 未登录, 跳转到【登录页】
		return new ModelAndView("redirect:/signin");
	}

	if (!user.isManager()) {
		// 权限不够, 返回403
		response.sendError(403);
		return null;
	}

	return new ModelAndViw("/profile.html", Map.of("user", user));
}


/*
【最后一步】是【在DispatcherServlet.init()方法中】初始化【所有Get和Post的映射】，以及用于【渲染的模板引擎】： */
public class DispatcherServlet extends HttpServlet { 
	private Map<String, GetDispatcher> getMappings = new HashMap<>();
	private Map<String, PostDispatcher> postMappings = new HashMap<>();
	private ViewEngine viewEngine;

	@Override
	public void init() throws ServletException {
		this.getMappings = scanGetInControllers();
		this.postMappings = scanPostInControllers();
		this.viewEngine = new ViewEngine(getServletContext());
	}

	//..
}


/*--------------------

Q: 如何扫描【所有Controller】, 以获取【所有标记有 @GetMapping 和 @PostMapping 的方法】？
A: 当然是使用【反射, reflect】了。

虽然代码比较繁琐，但我们相信各位童鞋可以【轻松实现】。

这样，【整个MVC框架】就搭建完毕。

======================================

#	实现渲染

有的童鞋对【如何使用模板引擎】进行【渲染】有疑问，即如何实现【上述的ViewEngine】？

其实【ViewEngine】非常简单，只需要实现【一个简单的render()方法】： */
public class ViewEngine {
	public void render(ModelAndView mv, Writer writer) throws IOException {

		String view = mv.view; // ModelAndView.view
		Map<String, Object> model = mv.model;

		// 【根据view】找到【模板文件】
		Template template = getTemplateByPath(view);

		// 【渲染】并【写入Writer】
		template.write(writer, model);
	}
}

/*
Java有很多【开源的模板引擎】，常用的有：
	· Thymeleaf
	· FreeMarker
	· Velocity

他们的【用法都大同小异】。

这里我们推荐一个【使用Jinja语法的模板引擎Pebble】，它的特点是【语法简单】，【支持模板继承】

编写出来的【模板】类似：
----------------------------------
<html>
<body>
  <ul>
  {% for user in users %}
    <li><a href="{{ user.url }}">{{ user.username }}</a></li>
  {% endfor %}
  </ul>
</body>
</html>
----------------------------------

即【变量】用{{ xxx }}表示，【控制语句】用{% xxx %}表示。

【使用Pebble渲染】只需要【如下几行代码】： */
public class ViewEngine {
	private final PebbleEngine engine;

	public ViewEngine(ServletContext servletContext) {
		// 定义一个ServletLoader用于【加载模板】
		ServletLoader loader = new ServletLoader(servletContext);

		// 模板编码
		loader.setCharset("UTF-8");

		// 模板前缀; 这里【默认模板】必须放在'/WEB-INF/templates/'下
		loader.setPrefix("/WEB-INF/templates");

		// 模板前缀;
		loader.setSuffix("");

		// 创建Pebble实例
		this.engine = new PebbleEngine.Builder()
			.autoEscaping(true) // 默认打开HTML字符转义,防止XSS攻击
			.cacheActive(false) // 禁用cache,使得每次修改模板可以立刻看到效果
			.loader(loader)
			.build();
	}

	public void render(ModelAndView mv, Writer writer) throws IOException {
		// 查找资源
		PebbleTemplate template = this.engine.getTemplate(mv.view);

		// 渲染
		template.evaluate(writer, mv.model);
	}
}


/*
最后我们来看看【整个工程的结构】：

web-mvc
├── pom.xml
└── src
    └── main
        ├── java
        │   └── com
        │       └── itranswarp
        │           └── learnjava
        │               ├── Main.java
        │               ├── bean
        │               │   ├── SignInBean.java
        │               │   └── User.java
        │               ├── controller
        │               │   ├── IndexController.java
        │               │   └── UserController.java
        │               └── framework
        │                   ├── DispatcherServlet.java
        │                   ├── FileServlet.java
        │                   ├── GetMapping.java
        │                   ├── ModelAndView.java
        │                   ├── PostMapping.java
        │                   └── ViewEngine.java
        └── webapp
            ├── WEB-INF
            │   ├── templates
            │   │   ├── _base.html
            │   │   ├── hello.html
            │   │   ├── index.html
            │   │   ├── profile.html
            │   │   └── signin.html
            │   └── web.xml
            └── static
                ├── css
                │   └── bootstrap.css
                └── js
                    ├── bootstrap.js
                    └── jquery.js


-------------------------------------------

其中，【framework包】是【MVC的框架】，完全可以【单独编译】后作为【一个Maven依赖】引入

【controller包】才是【我们需要编写的"业务逻辑"】。

我们还硬性规定【模板】必须放在【webapp/WEB-INF/templates/目录】下，【静态文件】必须放在【webapp/static/目录】下

因此，为了【便于开发】，我们还顺带【实现一个FileServlet】来处理【静态文件】： */
@WebServlet(urlPatterns = { "/favicon.ico", "/static/*" })
public class FileServlet extends HttpServlet {
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {

		// 读取当前的请求路径
		ServletContext ctx = req.getServletContext();

		// 【RequestURI】包含【ContextPath】需要去掉
		String urlPath = req.getRequestURI().substring(ctx.getContextPath().length());

		// 获取【真实文件路径】
		String filepath = ctx.getRealPath(urlPath);

		if (filepath == null) {
			// 无法获取到【路径】
			resp.sendError(HttpServletResponse.SC_NOT_FOUND);
			return ;
		}

		Path path = Paths.get(filepath);

		if (!path.toFile().isFile()) {
			// 文件不存在
			resp.sendError(HttpServletResponse.SC_NOT_FOUND);
			return ;
		}

		// 根据【文件名】猜测【Content-Type】
		String mime = Files.probeContentType(path);

		if (mime == null) {
			mime = "application/octet-stream";
		}
		resp.setContentType(mime);

		//读取文件,并写入Response
		OutputStream output = resp.getOutputStream();

		try (InputStream input = new BufferedInputStream(new FileInputStream(filepath))) {
			input.transferTo(output);
		}
		output.flush();
	}
}

/*
运行代码，

在浏览器中输入
	URLhttp://localhost:8080/hello?name=Bob

可以看到如下页面：
	2.png


--------------------------

为了把【方法参数的名称】编译到【class文件】中，以便【处理@GetMapping时】使用，

我们需要打开【编译器】的【一个参数】

# 在Eclipse中勾选
·	Preferences -> Java -> Compiler -> Store information about method parameters (usable via reflection)；

# 在Idea中选择
· Preferences -> Build, Execution,Deployment -> Compiler -> Java Compiler -> Additional command line parameters，填入-parameters；



在Maven的pom.xml添加一段配置如下：

<project ...>
    <modelVersion>4.0.0</modelVersion>
    ...
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <compilerArgs>
                        <arg>-parameters</arg>
                    </compilerArgs>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

有些用过Spring MVC的童鞋会发现，本节实现的这个【MVC框架】，【上层代码】使用的【公共类】如: GetMapping、PostMapping和ModelAndView都和【Spring MVC】非常类似。

实际上，我们这个【MVC框架】主要参考就是【Spring MVC】，通过【实现一个“简化版”MVC】，可以掌握【Java Web MVC开发的核心思想与原理】，对将来【直接使用Spring MVC】是【非常有帮助】的。


===============================================

#	练习

实现一个MVC框架 */



/*==============================================

#	小结

1. 【一个MVC框架】是【基于Servlet基础】抽象出【更高级的接口】，使得【上层】基于【MVC框架的开发】可以【不涉及Servlet相关的HttpServletRequest等】接口，处理【多个请求】更加灵活，并且可以使用【任意模板引擎】，【不必使用JSP】。


----------------------

汇总:


MVC代码模板: */

// ModelAndView: 包含1个View的路径 + 1个Model
public class ModelAndView { 
    Map<String, Object> model; // Map<model名, model对象>
    String view; // View路径
}


// Controller 各个控制器汇总
public class UserController {

    @GetMapping("/signin")
    public ModelAndView signin() {
        ...
    }

    @PostMapping("/signin")
    public ModelAndView doSignin(SignInBean bean) {
        ...
    }

    @GetMapping("/signout")
    public ModelAndView signout(HttpSession session) {
        ...
    }
}


// 分派器Servlet: 接收【所有Request】的Servlet (最复杂)
@WebServlet(urlPatterns = "/")
public class DispatcherServlet extends HttpServlet {

	// Map<路径, GET/POST分派器> || String 用于记录路径
    private Map<String, GetDispatcher> getMappings = new HashMap<>();
    	// GetDispatcher
    private Map<String, PostDispatcher> postMappings = new HashMap<>();
    	// PostDispatcher

    @Override
    public void init() throws ServletException {
        this.getMappings = scanGetInControllers(); 
        	// 获取所有Get的Map<String, GetDispatcher>
        this.postMappings = scanPostInControllers();
        	// 获取所有Post的Map<String, PostDispatcher>

        this.viewEngine = new ViewEngine(getServletContext());
        	//  public ViewEngine(ServletContext s)
    }

    // 仅做了doGet()一个例子
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        resp.setContentType("text/html");
        resp.setCharacterEncoding("UTF-8");

        String path = req.getRequestURI().substring(req.getContextPath().length());
        	// getContextPath();

    // 核心1: 根据【路径】查找【GetDispatcher】
        GetDispatcher dispatcher = this.getMappings.get(path);
        	// Map<String, GetDispatcher>.get(path)

        if (dispatcher == null) {
            // 未找到返回404:
            resp.sendError(404);
            return;
        }

    // 核心2: 调用Controller.invoke(), return ModelAndView:
        ModelAndView mv = dispatcher.invoke(req, resp);

        // 允许返回null:
        if (mv == null) {
            return;
        }

        // 允许返回【'redirect:'开头的view】, 表示【重定向】:
        if (mv.view.startsWith("redirect:")) {
            resp.sendRedirect(mv.view.substring(9));
            return;
        }

    // 核心3: 将【模板引擎】渲染的内容【写入响应】:
        PrintWriter pw = resp.getWriter();
        this.viewEngine.render(mv, pw);
        pw.flush();
    }
}


/*-------------------------------------
#	分派器

构造【某个方法】需要的【所有参数列表】,

使用【反射】调用【该方法】后
	this.method.invoke(this.instance, arguments);
	--> 即 this.选择的Controller实例.invoke(this, arguments[]参数列表)

返回【结果】。 */

// Get 分派器
class GetDispatcher {
    Object instance; // Controller_Instance
    Method method; // Controller_Method

    Class<?>[] parameterClasses; // 参数类型
    String[] parameterNames; // 参数名称(即path?p=1&a=2的p,a)

    // invoke(),处理真正的请求
    public ModelAndView invoke(HttpServletRequest request, HttpServletResponse response) {

        Object[] arguments = new Object[parameterClasses.length];

        for (int i=0; i<parameterClasses.length; i++) {

            String parameterName = parameterNames[i];
            Class<?> parameterClass = parameterClasses[i];

            if (parameterClass == HttpServletRequest.class) {
                arguments[i] = request;
            } else if (parameterClass == HttpServletResponse.class) {
                arguments[i] = response;
            } else if (parameterClass == HttpSession.class) {
                arguments[i] = request.getSession();
            } else if (parameterClass == int.class) {
                arguments[i] = Integer.valueOf(getOrDefault(request, parameterName, "0"));
            } else if (parameterClass == long.class) {
                arguments[i] = Long.valueOf(getOrDefault(request, parameterName, "0"));
            } else if (parameterClass == boolean.class) {
                arguments[i] = Boolean.valueOf(getOrDefault(request, parameterName, "false"));
            } else if (parameterClass == String.class) {
                arguments[i] = getOrDefault(request, parameterName, "");
            } else {
                throw new RuntimeException("Missing handler for type: " + parameterClass);
            }
        }

        return (ModelAndView) this.method.invoke(this.instance, arguments);
    }

    // 获取or默认值
    private String getOrDefault(HttpServletRequest request, String name, String defaultValue) {
        String s = request.getParameter(name);
        return s == null ? defaultValue : s;
    }
}


// Post 分派器
class PostDispatcher {
    Object instance; // Controller实例
    Method method; // Controller方法

    Class<?>[] parameterClasses; // 方法参数类型
    ObjectMapper objectMapper; // JSON映射(Post附带的JSON{'x':1,'y':2})
    	// Post中,【所有数据】都应当从【Post Body】中读取

    public ModelAndView invoke(HttpServletRequest request, HttpServletResponse response) {

    	// 存取参数
        Object[] arguments = new Object[parameterClasses.length];

        for (int i = 0; i < parameterClasses.length; i++) {

            Class<?> parameterClass = parameterClasses[i];

            if (parameterClass == HttpServletRequest.class) {
                arguments[i] = request;
            } else if (parameterClass == HttpServletResponse.class) {
                arguments[i] = response;
            } else if (parameterClass == HttpSession.class) {
                arguments[i] = request.getSession();
            } else {
                // 【读取JSON】并解析为【JavaBean】
                BufferedReader reader = request.getReader();

                arguments[i] = this.objectMapper.readValue(reader, parameterClass);
            }
        }
        
        return (ModelAndView) this.method.invoke(instance, arguments);
    }
}


// 实现渲染
public class ViewEngine {

    private final PebbleEngine engine; // 视图引擎

    public ViewEngine(ServletContext servletContext) {
        // 定义一个ServletLoader用于加载模板:
        ServletLoader loader = new ServletLoader(servletContext);

        // 模板编码
        loader.setCharset("UTF-8");
        // 模板前缀(这里默认模板必须放在'/WEB-INF/templates/'目录)
        loader.setPrefix("/WEB-INF/templates");
        // 模板后缀
        loader.setSuffix("");
        // 创建【Pebble实例】
        this.engine = new PebbleEngine.Builder()
            .autoEscaping(true) // 默认打开HTML字符转义，防止XSS攻击
            .cacheActive(false) // 禁用缓存使得每次修改模板可以立刻看到效果
            .loader(loader).build();
    }

    // 视图【渲染】
    public void render(ModelAndView mv, Writer writer) throws IOException {
        // 查找【模板】:
        PebbleTemplate template = this.engine.getTemplate(mv.view);
        	// PebbleEngine.getTemplate(ModelAndView.view路径)

        // 【渲染】
        template.evaluate(writer, mv.model);
        	// PebbleTemplate.evaluate(Writer, ModelAndView.model)
        	// model = Map<String, Object>
    }
}


// 处理【静态文件】
@WebServlet(urlPatterns = { "/favicon.ico", "/static/*" })
public class FileServlet extends HttpServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 读取当前请求路径:
        ServletContext ctx = req.getServletContext();
        // RequestURI包含ContextPath,需要去掉:
        String urlPath = req.getRequestURI().substring(ctx.getContextPath().length());
        // 获取真实文件路径:
        String filepath = ctx.getRealPath(urlPath);
        if (filepath == null) {
            // 无法获取到路径:
            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }
        Path path = Paths.get(filepath);
        if (!path.toFile().isFile()) {
            // 文件不存在:
            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }
        // 根据文件名猜测Content-Type:
        String mime = Files.probeContentType(path);
        if (mime == null) {
            mime = "application/octet-stream";
        }
        resp.setContentType(mime);
        // 读取文件并写入Response:
        OutputStream output = resp.getOutputStream();
        try (InputStream input = new BufferedInputStream(new FileInputStream(filepath))) {
            input.transferTo(output);
        }
        output.flush();
    }
}

// profile 简介,概述
@GetMapping("/user/profile")
public ModelAndView profile(HttpServletResponse response, HttpSession session) {

    User user = (User) session.getAttribute("user");

    if (user == null) {
        // 未登录, 跳转到登录页:
        return new ModelAndView("redirect:/signin");
    }

    if (!user.isManager()) {
        // 权限不够, 返回403:
        response.sendError(403);
        return null;
    }

    return new ModelAndView("/profile.html", Map.of("user", user));
}


/*
【整个工程的结构】：

web-mvc
├── pom.xml
└── src
    └── main
        ├── java
        │   └── com
        │       └── itranswarp
        │           └── learnjava
        │               ├── Main.java
        │               ├── bean
        │               │   ├── SignInBean.java
        │               │   └── User.java
        │               ├── controller (需要编写的业务)
        │               │   ├── IndexController.java
        │               │   └── UserController.java
        │               └── framework (MVC的框架)
        │                   ├── DispatcherServlet.java
        │                   ├── FileServlet.java (处理静态文件)
        │                   ├── GetMapping.java
        │                   ├── ModelAndView.java
        │                   ├── PostMapping.java
        │                   └── ViewEngine.java
        └── webapp
            ├── WEB-INF
            │   ├── templates (模板)
            │   │   ├── _base.html
            │   │   ├── hello.html
            │   │   ├── index.html
            │   │   ├── profile.html
            │   │   └── signin.html
            │   └── web.xml
            └── static (静态文件)
                ├── css
                │   └── bootstrap.css
                └── js
                    ├── bootstrap.js
                    └── jquery.js


-----------------------------

# 在Eclipse中勾选
·	Preferences -> Java -> Compiler -> Store information about method parameters (usable via reflection)；

# 在Idea中选择
· Preferences -> Build, Execution,Deployment -> Compiler -> Java Compiler -> Additional command line parameters，填入-parameters；


在Maven的【pom.xml】添加【一段配置】如下：

<project ...>
    <modelVersion>4.0.0</modelVersion>
    ...
    <build>		<---
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <compilerArgs>
                        <arg>-parameters</arg>
                    </compilerArgs>
                </configuration>
            </plugin>
        </plugins>
    </build>	<---
</project>



*/